﻿using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using Baik.Interface.Execution;
using Baik.Interface.Framework;

namespace Baik.Execution
{
    public class Number : BuiltInObject, INumber
    {
        public IExceptionThrower Thrower { get; private set; }
        public Number(IInjector inject, IExceptionThrower thrower) : base(inject)
        {
            Thrower = thrower;
            Dictionary.Add("round",  BuiltInFactory.Function(inject, Round));
            Dictionary.Add("sum",  BuiltInFactory.Function(inject, Sum));
            Dictionary.Add("minus",  BuiltInFactory.Function(inject, Minus));
            Dictionary.Add("multiply",  BuiltInFactory.Function(inject, Multiply));
            Dictionary.Add("divide",  BuiltInFactory.Function(inject, Divide));
            Dictionary.Add("power",  BuiltInFactory.Function(inject, Power));
            Dictionary.Add("floor",  BuiltInFactory.Function(inject, Floor));
            Dictionary.Add("ceiling",  BuiltInFactory.Function(inject, Ceiling));
            Dictionary.Add("log",  BuiltInFactory.Function(inject, Log));
            Dictionary.Add("root",  BuiltInFactory.Function(inject, Root));
            Dictionary.Add("lessThan",  BuiltInFactory.Function(inject, LessThan));
            Dictionary.Add("greaterThan",  BuiltInFactory.Function(inject, GreaterThan));
            Dictionary.Add("lessThanOrEqualTo",  BuiltInFactory.Function(inject, LessThanOrEquals));
            Dictionary.Add("greaterThanOrEqualTo",  BuiltInFactory.Function(inject, GreaterThanOrEquals));
            this.Assign("equals",  BuiltInFactory.Function(inject, BuiltinEquals));
        }

       
        private IObject BuiltinEquals(IEnumerable<IObject> arg)
        {
            foreach (var obj in arg)
            {
                if (!(obj is INumber)) { Thrower.Throw("you can only compare numbers to numbers"); }
                if (Math.Abs(((INumber)obj).Value - Value) > 0.000001) return Inject.Instantiate<IFalse>();
            }
            return Inject.Instantiate<ITrue>();
        }

        private IObject GreaterThanOrEquals(IEnumerable<IObject> arg)
        {
            foreach (var obj in arg)
            {
                if (!(obj is INumber)) { Thrower.Throw("you can only compare numbers to numbers"); }
                if ((((INumber)obj).Value - Value) <= 0) return Inject.Instantiate<IFalse>();
            }
            return Inject.Instantiate<ITrue>(); ;
        }

        private IObject LessThanOrEquals(IEnumerable<IObject> arg)
        {
            foreach (var obj in arg)
            {
                if (!(obj is INumber)) { Thrower.Throw("you can only compare numbers to numbers"); }
                if ((((INumber)obj).Value - Value) >= 0) return Inject.Instantiate<IFalse>();
            }
            return Inject.Instantiate<ITrue>(); ;
        }

        private IObject GreaterThan(IEnumerable<IObject> arg)
        {
            foreach (var obj in arg)
            {
                if (!(obj is INumber)) { Thrower.Throw("you can only compare numbers to numbers"); }
                if ((((INumber)obj).Value - Value) < 0) return Inject.Instantiate<IFalse>();
            }
            return Inject.Instantiate<ITrue>(); ;
        }

        private IObject LessThan(IEnumerable<IObject> arg)
        {
            foreach (var obj in arg)
            {
                if (!(obj is INumber)) { Thrower.Throw("you can only compare numbers to numbers"); }
                if ((((INumber)obj).Value - Value) > 0) return Inject.Instantiate<IFalse>();
            }
            return Inject.Instantiate<ITrue>(); ;
        }

        private IObject Root(IEnumerable<IObject> arg)
        {
            var rtn = Inject.Instantiate<INumber>();
            var rtnval = Math.Sqrt(Value);
            return rtn;
        }

        private IObject Log(IEnumerable<IObject> arg)
        {
            var rtn = Inject.Instantiate<INumber>();
            var rtnval = Value;
            arg.OfType<INumber>().Select(n => n.Value).ToList().ForEach(val => rtnval = Math.Log(rtnval, val));
            rtn.Value = rtnval;
            return rtn;
        }

        private IObject Ceiling(IEnumerable<IObject> arg)
        {
            var rtn = Inject.Instantiate<INumber>();
            var rtnval = Math.Ceiling(rtn.Value);
            return rtn;
        }

        private IObject Floor(IEnumerable<IObject> arg)
        {
            var rtn = Inject.Instantiate<INumber>();
            var rtnval = Math.Floor(rtn.Value);
            return rtn;
        }

        private IObject Power(IEnumerable<IObject> arg)
        {
            var rtn = Inject.Instantiate<INumber>();
            var rtnval = Value;
            arg.OfType<INumber>().Select(n => n.Value).ToList().ForEach(val => rtnval = Math.Pow(rtnval, val));
            rtn.Value = rtnval;
            return rtn;
        }

        private IObject Divide(IEnumerable<IObject> arg)
        {
            var rtn = Inject.Instantiate<INumber>();
            var rtnval = Value;
            arg.OfType<INumber>().Select(n => n.Value).ToList().ForEach(val => rtnval /= val);
            rtn.Value = rtnval;
            return rtn;
        }

        private IObject Multiply(IEnumerable<IObject> arg)
        {
            var rtn = Inject.Instantiate<INumber>();
            var rtnval = Value;
            arg.OfType<INumber>().Select(n => n.Value).ToList().ForEach(val => rtnval *= val);
            rtn.Value = rtnval;
            return rtn;
        }

        private IObject Minus(IEnumerable<IObject> arg)
        {
            var rtn = Inject.Instantiate<INumber>();
            var rtnval = Value;
            arg.OfType<INumber>().Select(n => n.Value).ToList().ForEach(val => rtnval -= val);
            rtn.Value = rtnval;
            return rtn;
        }

        private IObject Sum(IEnumerable<IObject> arg)
        {
            var rtn = Inject.Instantiate<INumber>();
            var rtnval = Value;
            arg.OfType<INumber>().Select(n => n.Value).ToList().ForEach(val => rtnval += val);
            rtn.Value = rtnval;
            return rtn;
        }

        private IObject Round(IEnumerable<IObject> arg)
        {
            var rtn = Inject.Instantiate<INumber>();
            rtn.Value = Math.Round(Value);
            return rtn;
        }

        public double Value { get; set; }
    }
}